/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.dotaznikserver;

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.util.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.prefs.Preferences;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * Třída vytvářející GUI, udržující všechny databáze.
 * Po celou dobu běhu programu existuje v právě jedné instanci.
 * @author Jan Hamáček
 */
public class UI {
    
    /**
     * Vytvoří grafické rozhraní. Musí být zavolán ze swing vlákna.
     */
    public void createAndShowGUI(){
        try{
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }catch(Exception e){}
        
        frame = new JFrame("Server");
        String[] imageNames = {"Ikona64.png", "Ikona32.png", "Ikona16.png", "Ikona.ico"};
        List<Image> images = new ArrayList<>(imageNames.length);
        for(String name:imageNames){
            images.add(Toolkit.getDefaultToolkit().getImage(name));
        }
        frame.setIconImages(images);
        
        frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
        frame.setPreferredSize(new Dimension(800, 600));
        frame.setJMenuBar(createMenuBar());
        if(SystemTray.isSupported()){
            createTray();
        }
        Container root = frame.getContentPane();
        root.setLayout(new BorderLayout());
        root.add(createToolbar(), BorderLayout.NORTH);
        JSplitPane center = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
        leftSide = new JTabbedPane(JTabbedPane.TOP);
        JPanel computerList = createComputerPanel();
        questionList = new JList<>(questions);
        questionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        leftSide.addTab("Počítače", computerList);
        leftSide.addTab("Výsledky", questionList);
        questionList.addListSelectionListener(new questionSelected());
        center.setLeftComponent(leftSide);
        contentPanel = new JPanel();
        contentLayout = new CardLayout();
        contentPanel.setLayout(contentLayout);
        JScrollPane contentScrollPane = new JScrollPane(contentPanel);
        center.setRightComponent(contentScrollPane);
        root.add(center, BorderLayout.CENTER);
        statusBar = new StatusBar();
        root.add(statusBar, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
        try{
            loadComputers();
        }catch(IOException e){
            JOptionPane.showMessageDialog(frame,
                    "Nepodařilo se načíst soubor se seznamem počítačů.",
                    "Chyba při načítání", JOptionPane.ERROR_MESSAGE);
        }
        
        /* před koncem pošli všem klientům STOP
        * ten stopPressed, když je takhle zavolán, pracuje se statusBarem mimo swing vlákno
        * ale to už by těsně před koncem mělo být jedno.
        */
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                new stopPressed().actionPerformed(new ActionEvent(this, 0, "shutdown"));
            }
        });
    }
    
    private JPanel createComputerPanel(){
        JPanel computerPanel = new JPanel(new BorderLayout());
        JPanel tools = new JPanel(new FlowLayout());
        JButton pridej = new JButton("Nový");
        pridej.addActionListener(new newComputerPressed());
        JButton odeber = new JButton("Smazat");
        connectionList = new JList<>(connections);
        odeber.addActionListener(new deleteComputerPressed());
        connectionList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        tools.add(pridej);
        tools.add(odeber);
        computerPanel.add(tools, BorderLayout.NORTH);
        computerPanel.add(connectionList, BorderLayout.CENTER);
        
        return computerPanel;
    }
    
    private JToolBar createToolbar(){
        JToolBar toolbar = new JToolBar("Nová otázka");
        JLabel newLabel = new JLabel("Nový: ");
        JButton YNButton = new JButton("Ano/ne");
        YNButton.addActionListener(new createYNPressed());
        JButton radioButton = new JButton("Výběr z několika možností");
        radioButton.addActionListener(new createRadioPressed());
        JButton textButton = new JButton("Textová odpověď");
        textButton.addActionListener(new createTextPressed());
        JButton cancelButton = new JButton("Zrušit vše");
        cancelButton.addActionListener(new stopPressed());
        JButton nameButton = new JButton("Zeptat se na jména");
        nameButton.addActionListener(new askForNamePressed());
        toolbar.add(newLabel);
        toolbar.add(YNButton);
        toolbar.add(radioButton);
        toolbar.add(textButton);
        toolbar.add(nameButton);
        toolbar.add(cancelButton);
        toolbar.setFloatable(true);
        return toolbar;
    }
    
    private void createTray(){
        try{
            SystemTray tray = SystemTray.getSystemTray();
            

            Image image = Toolkit.getDefaultToolkit().getImage("Ikona32.png");
            PopupMenu popup = new PopupMenu();
            MenuItem item = new MenuItem("Zobrazit okno");
            item.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    frame.setVisible(true);
                }
            });
            popup.add(item);
            item = new MenuItem("-");
            popup.add(item);
            item = new MenuItem("Nový ano/ne");
            item.addActionListener(new createYNPressed());
            popup.add(item);
            item = new MenuItem("Nový výběr z možností");
            item.addActionListener(new createTextPressed());
            popup.add(item);
            item = new MenuItem("Nový textový dotaz");
            item.addActionListener(new createTextPressed());
            popup.add(item);
            item = new MenuItem("Zeptat se na jména");
            item.addActionListener(new askForNamePressed());
            popup.add(item);
            item = new MenuItem("Zrušit vše");
            item.addActionListener(new stopPressed());
            popup.add(item);
            item = new MenuItem("-");
            popup.add(item);
            item = new MenuItem("Ukončit program");
            item.addActionListener(new exitPressed());
            popup.add(item);
            trayIcon = new TrayIcon(image, "Tray Icon", popup);
            trayIcon.setImageAutoSize(true);
            trayIcon.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                   frame.setVisible(true);
                }
            });
            tray.add(trayIcon);
        }catch(AWTException e){}
    }
    
    private JMenuBar createMenuBar(){
        JMenuBar menu = new JMenuBar();
        JMenu file = new JMenu("Soubor");
        JMenuItem settings = new JMenuItem("Nastavení");
        settings.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try{
                    saveComputers();
                } catch(IOException ex){}
                PreferenceDialog d = new PreferenceDialog(frame, "Nastavení");
                d.setVisible(true);
                if(d.newComputerListSet()){
                    try{
                        loadComputers();
                    }catch(IOException ex){
                        JOptionPane.showMessageDialog(frame,
                    "Nepodařilo se načíst soubor se seznamem počítačů.",
                    "Chyba při načítání", JOptionPane.ERROR_MESSAGE);
                    }
                }
            }
        });
        JMenuItem exit = new JMenuItem("Ukončit");
        exit.addActionListener(new exitPressed());
        file.add(settings);
        file.add(exit);
        JMenu newQuestion = new JMenu("Otázka");
        JMenuItem YNItem = new JMenuItem("Ano/Ne");
        YNItem.addActionListener(new createYNPressed());
        JMenuItem multiple = new JMenuItem("Více možností");
        multiple.addActionListener(new createRadioPressed());
        JMenuItem text = new JMenuItem("Textová odpověď");
        text.addActionListener(new createTextPressed());
        JMenuItem name = new JMenuItem("Zeptat se na jména");
        name.addActionListener(new askForNamePressed());
        JMenuItem cancel = new JMenuItem("Zrušit vše");
        cancel.addActionListener(new stopPressed());
        newQuestion.add(YNItem);
        newQuestion.add(multiple);
        newQuestion.add(text);
        newQuestion.add(name);
        newQuestion.add(cancel);
        menu.add(file);
        menu.add(newQuestion);
        return menu;
    }
    
    private class createYNPressed implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            YNQuestion q = QuestionDialogs.showYNQuestionDialog(frame);
            if (q == null){
                return;
            }
            try{
                layoutQuestion(q);
            }catch(UsedIdException ex){}
        }
        
    }
    
    private class createTextPressed implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            TextQuestion q = QuestionDialogs.showTextQuestionDialog(frame);
            if(q == null){
                return;
            }
            try{
                layoutQuestion(q);
            }catch(UsedIdException ex){}
        }
    
    }
    
    private class createRadioPressed implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            RadioQuestion q = QuestionDialogs.showRadioQuestionDialog(frame);
            if(q == null){
                return;
            }
            try{
                layoutQuestion(q);
            }catch(UsedIdException ex){}
        }
    }
    
    private class askForNamePressed implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            NameQuestion q = new NameQuestion(-1);
            try{
                int id = replySorter.getFreeId();
                q.setId(id);
                replySorter.registerQuestion(q);
                sendQuestion(q);
            }catch(UsedIdException ex){
                
            }
        }
        
    }
    
    private class stopPressed implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            statusBar.flash("Zastavuji všechny dotazy.");
            for(final Connection c:connections){
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            c.writeLine("STOP");
                        }catch(IOException e){}
                    }
                });
                t.start();
            }
        }
    }
    
    private class exitPressed implements ActionListener{
        @Override
        public void actionPerformed(ActionEvent e) {
            try{
                saveComputers();
            }catch(IOException ex){};
            System.exit(0);
        }
        
    }
    
    private class newComputerPressed implements ActionListener{
        
        @Override
        public void actionPerformed(ActionEvent e) {
            String address = JOptionPane.showInputDialog(frame, "Zadejte adresu nebo síťové jméno cílového počítače.", "Nový počítač", JOptionPane.QUESTION_MESSAGE);
            if (address == null){
                return;
            }
            try{
                InetAddress adr = InetAddress.getByName(address);
                Connection c = new Connection(adr);
                connections.addElement(c);
                c.addDataRecievedListener(replySorter);
            }catch(UnknownHostException ex){
                JOptionPane.showMessageDialog(frame
                        , "Počítač "+address+" nebyl nebyl nalezen v síti. Nemůžu ho přidat"
                        , "Počítač nenalezen", JOptionPane.ERROR_MESSAGE);
            }
            
        }
        
    }
    
    private class deleteComputerPressed implements ActionListener{

        @Override
        public void actionPerformed(ActionEvent e) {
            int sel = connectionList.getSelectedIndex();
            connections.remove(sel);
            
        }
        
    }
    
    private class questionSelected implements ListSelectionListener{

        @Override
        public void valueChanged(ListSelectionEvent e) {
            int index = questionList.getSelectedIndex();
            Question q = questions.get(index);
            contentLayout.show(contentPanel, new Integer(q.getId()).toString());
        }
        
    }
    
    // WARNING: UsedIdException by nikdy nemělo nastat layoutQuestion nepotřebuje throws.
    /**
     * Zobrazí otázku v GUI aplikace. Přidá do seznamu proběhlých otázek nový záznam
     * a zobrazí ho. Navíc přihlásí otázku k příjmu odpovědí se stejným id jako
     * Q.getId()
     * @param q Otázka k zobrazení. Současné id otázky bude ignorováno a nahrazeno novým,
     * volným.
     * @throws UsedIdException Nemělo by nikdy nastat.
     */
    private void layoutQuestion(Question q) throws UsedIdException{
        q.setId(replySorter.getFreeId());
        replySorter.registerQuestion(q);
        questions.addElement(q);
        contentPanel.add(q.getContainer(), (new Integer(q.getId())).toString());
        contentLayout.addLayoutComponent(q.getContainer(), (new Integer(q.getId())).toString());
        selectLastQuestion();
        sendQuestion(q);
    }
    
    
    /**
     * Uloží současný seznam počítačů do souboru na adrese preference "ComputersFile"
     * @throws IOException Pokud se nepodařilo otevřít soubor pro zápis.
     */
    public void saveComputers() throws IOException{
        Preferences prefs = Preferences.userNodeForPackage(UI.class);
        InetAddress[] addresses = new InetAddress[connections.size()];
        for(int i = 0; i < connections.size(); i++){
            addresses[i] = connections.get(i).getInetAddress();
        }
        String computerFileName = prefs.get("ComputersFile", "computers.list");
        ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream(computerFileName));
        out.writeObject(addresses);
        out.flush();
        out.close();
    }
    
    /**
     * Načte seznam souborů ze souboru na adrese preference "ComputersFile".
     * @throws IOException Pokud nastala chyba při otvírání souboru. Pokud soubor neexistoval, načte se prázdný seznam souborů a nevyhazuje se výjimka.
     */
    public void loadComputers() throws IOException{
        Preferences prefs = Preferences.userNodeForPackage(UI.class);
        InetAddress[] addresses;
        String computerFileName = prefs.get("ComputersFile", "computers.list");
        try{
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(computerFileName));
            addresses = (InetAddress[])in.readObject();

        }catch(FileNotFoundException | ClassNotFoundException e){
            addresses = new InetAddress[0];
        }
        connections.clear();
        
        for(int i = 0; i < addresses.length; i++){
            Connection c = new Connection(addresses[i]);
            c.addDataRecievedListener(replySorter);
            connections.addElement(c);
        }
    }
    
    /**
     * Odesílá otázku předanou v konstruktoru přes spojení c. Předpokládá, že lefttosend
     * je počitadlo ještě neodeslaných otázek. Proto když lefttosend klesne k 0, zobrazí
     * zprávu, že všechno je odesláno.
     */
    private class QuestionSender extends SwingWorker<Boolean, Object>{
        private Question q;
        private Connection c;
        
        public QuestionSender(Question q, Connection c) {
            this.q = q;
            this.c = c;
        }
        
        @Override
        protected Boolean doInBackground() throws Exception {
            try{
                q.send(c);
            }catch(IOException e){
                return false;
            }
            return true;
        }

        @Override
        protected void done() {
            // toto by teoreticky mělo být synchronizováno.
            // Nedělám to, protože nevadí, když se flashne víckrát.
            leftToSend--;
            if(leftToSend == 0){
                statusBar.flash("Odesílání hotovo");
            }
        }
        
    }
    
    private void selectLastQuestion(){
        if(questions.getSize() == 0)
            return;
        leftSide.setSelectedComponent(this.questionList);
        questionList.setSelectedIndex(questions.getSize()-1);
    }
    
    private void sendQuestion(Question q){
        leftToSend += connections.getSize();
        statusBar.setText("Otázka se posílá...");
        for(int i = 0; i < connections.getSize(); i++){
            QuestionSender qs = new QuestionSender(q, connections.elementAt(i));
            qs.execute();
        }
    }
    
        
    
    
    private ReplySorter replySorter = new ReplySorter();
    private ConnectionListModel connections = new ConnectionListModel();
    private DefaultListModel<Question> questions = new DefaultListModel<>();
    
    
    private JFrame frame = null;
    private TrayIcon trayIcon = null;
    private JPanel contentPanel = null;
    private CardLayout contentLayout = null;
    private StatusBar statusBar = null;
    private JTabbedPane leftSide = null;
    
    private javax.swing.JList<Connection> connectionList = null;
    private javax.swing.JList<Question> questionList = null;
    
    // pouziva se k pocitani zbylych QuestionSenderu, aby ten posledni updatnul UI
    private volatile int leftToSend = 0;
}
